#include "record_mp4_blk.h"
#include "frame_utility.h"
#include <stdio.h>

#include "cross_com.h"
#include <mutex>
#include <thread>
#include <errno.h>
#include <time.h>
#include <chrono>
#include "storage_api.h"
#ifndef _WIN32
#include <stdlib.h>
#include <mntent.h>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
#include "mp4_pkt.h"
#ifdef __cplusplus
}
#endif

 mutex CRecord_mp4_blk::st_cycle_mutex;


int CRecord_mp4_blk::st_fill_len;
extern CIndexSet* g_pIndexSet;

CRecord_mp4_blk::CRecord_mp4_blk()
{
	m_width     = 1920;
	m_height    = 1080;
	m_frame_rate = 25;
	m_start_tm = 0;
	m_bOpen = false;
	m_prepare_sec = 6;
	m_event_id = 0;
	strcpy(m_file_ext,"mp4");
	strcpy(m_event_name,"NORMAL");
	m_client_id = 0;
	m_packet_sec = 600;
	m_show_log = false;
	m_stream = 0;

	m_pkting.store(false);
}

CRecord_mp4_blk::~CRecord_mp4_blk()
{
	printf("CRecord_mp4_blk::~CRecord_mp4_blk exit .\n");
}

int CRecord_mp4_blk::Init(string  file_path, string file_ext,int stream_id, int packet_sec)
{
	m_storage_path = file_path;
#ifndef _WIN32
	m_storage_path += "/";
#endif
	m_packet_sec = packet_sec;
	m_stream = stream_id;
	if (!file_ext.empty())
	{
		strcpy(m_file_ext, file_ext.c_str());
	}
	printf("CRecord_mp4_blk::Init file_path %s  file_ext %s stream %d m_packet_sec %d  .\n",
		file_path.c_str(), file_ext.c_str(), stream_id, m_packet_sec);

	char dev_usb[64] = { 0 };
	char mnt_dir[128] = { 0 };

	if (0== check_path(file_path.c_str()))
	{
#ifndef _WIN32
		get_usb_path(dev_usb, mnt_dir);
	
		strcpy(m_disk_dev, dev_usb);
		strcpy(mnt_dir,file_path.c_str());
#else
		strcpy(m_disk_dev,file_path.c_str());
#endif
	}

	m_storage_path = file_path;
#ifndef _WIN32
	m_storage_path += "/";
#endif


	m_pRecIndexSet = g_pIndexSet;
	return 0;
}

int CRecord_mp4_blk::Start(int stream_id)
{
	printf(" CRecordWarning::Start .\n");
	if (stream_id != -1)
	{
		m_stream = stream_id;
	}
	if (m_pkting)
	{
		return -1;
	}
	m_recThd = std::thread(&CRecord_mp4_blk::DataProcThread, this);
	m_recThd.detach();

	return 0;
}

int CRecord_mp4_blk::Stop()
{
	m_bStart = false;
	return 0;
}


int CRecord_mp4_blk::DataProcThread()
{
	int buf_size = SIZE_KB(800);
	int ret = 0;
	unsigned char * pBuf = new unsigned char[buf_size];

	string destFile;
	int frame_sz;
	int64_t cur_pts = 0;
	int64_t	last_pts = 0;
	int64_t diff = 0;
	CFrmUtility frmTool;
	int   width = 1920;
	int   height = 1080;
	int frmType = PFrame;
	int keyfame = false;
	unsigned char* s_buffer = new unsigned char[1024 * 1024 * 1];
	//CMp4_h26x_box  mp4_file;
	printf("CRecord_mp4_blk::DataProcThread start m_event_id %d m_bStart %d m_bOpen %d .\n",
		m_event_id,
		m_bStart,
		m_bOpen
	);

	char disk_dev[64] = { 0 };
	char mnt_dir[128] = { 0 };
	int try_cnt = 32;
	uint32_t frm_cnt = 0;
	struct mov_h264_test_t ctx;
	unsigned char s_extra_data[64 * 1024] = {0};

	if (0 != access(m_disk_dev, 0))
	{
	RE_CHECK:
		while (true)
		{
			memset(disk_dev, 0, 64);
			memset(mnt_dir, 0, 128);

			if (0 == check_path(m_storage_path.c_str()))
			{
				
				break;
			}
			get_usb_path(disk_dev, mnt_dir);
			if (0 == access(mnt_dir, 0))
			{
				m_storage_path = mnt_dir;
				//	m_storage_path += "normal/";
				break;
			}
			printf("-------WriteDataProcmp4  will scan disk  m_storage_path %s .-------\n", m_storage_path.c_str());
			this_thread::sleep_for(chrono::milliseconds(3000));
		}
	}
	int vcl = 0;
	int update = 0;
	int  n;
	int64_t diff_pts = 0;
	FILE* fp = NULL;
	int client = 0;
	int file_pos = 0;
	while (true)
	{
		//printf("STFS_FifoReadFrame try_cnt %d .\n", try_cnt);
		frame_sz = ret = STFS_FifoReadFrame((uint8_t*)pBuf, buf_size, m_stream, client);
		if (try_cnt >= 0)
		{
			try_cnt--;
		}
		if (ret <= 0)
		{
			this_thread::sleep_for(chrono::milliseconds(30));
			if (m_show_log)
			{
				printf("ReadFrame fail ret %d  buf_size %d m_stream %d m_client_id %d  .\n",
					ret, buf_size, m_stream, m_client_id);
			}
			continue;
		}
		frmTool.Init(pBuf);

		cur_pts = frmTool.GetFrameTime();

		if(!m_bOpen)
		{
			frmType = frmTool.GetFrameType();
			if (frmType != IFrame)
			{
				continue;
			}
OPEN_NEW_FILE:

			m_start_tm = cur_pts;
			PrintTimeMs(cur_pts, 0, "Start packet mp4");
			int64_t file_tm = 0;

			GetDvrCurTime(&file_tm);
			GetNextFileName(destFile, file_tm, m_event_id);
			m_frame_rate = 30;
			width = frmTool.GetVedioWidth();
			height = frmTool.GetVedioHeight();
			printf("WriteDataProcMp4 CreateMP4File %s  m_frame_rate %d width %d height %d cur_pts %lld.\n", destFile.c_str(),
				m_frame_rate, width, height, cur_pts);
			memset(&ctx, 0, sizeof(ctx));
			ctx.track = ctx.trackAudio = -1;
			ctx.width = 1920;
			ctx.height = 1080;
			fp = NULL;
		
			fp = fopen(destFile.c_str(), "wb+");
			if (fp == NULL)
			{

				break;
			}
			ctx.mov = mov_writer_create(mov_file_buffer(), fp, MOV_FLAG_FASTSTART);
			if (ctx.mov == NULL)
			{
				TRACE_ERR("open file failed.\n");
				this_thread::sleep_for(chrono::milliseconds(1000));
				continue;
			}
			rec_mark_start(m_fileIndex, cur_pts);
			m_mutex.lock();
			m_bOpen = true;
			m_mutex.unlock();
		}

		if (frmTool.GetFrameType() == AFrame)
		{  //��Ƶ
			continue;
		}
		frame_sz = frmTool.GetFrameLen(false);
		if (frmTool.GetFrameType() == IFrame)
		{
			keyfame = true;
			if (m_show_log)
			{
				PrintTimeMs(cur_pts, 0, "cur Iframe time");
				printf("IFrame size %d frm no %d  \n", frame_sz, frmTool.GetFrameNo());
			}
		}
		int codec_t = 0;// frmTool.GetCodecType();
		void * pData = pBuf + frmTool.GetFrameHeadLen();

		if (codec_t == 0)
			n = h264_annexbtomp4(&ctx.avc, pData, frame_sz, s_buffer, 1024 * 1024 * 2, &vcl, &update);
		else if (codec_t == 1)
			n = h265_annexbtomp4(&ctx.hevc, pData, frame_sz, s_buffer, 1024 * 1024 * 2, &vcl, &update);

		if (!init_av_track(ctx, codec_t, s_extra_data))
		{
			continue;
		}
		file_pos = ftell(fp);
		if (file_pos + n + (150<<10) >= (m_file_len ))
		{
			printf("ch%02d keep_sec %d prepare_sec %d  frame sz %d frmType %c  CloseMP4File diff %lld rec_sec_ms %lld  frm_cnt %d \n",
				m_stream, m_packet_sec, m_prepare_sec, frame_sz, frmType, diff, m_packet_sec, frm_cnt);
			if (m_show_log)
				PrintTimeMs(last_pts, 0, "last frame time");
			m_mutex.lock();
			mov_writer_destroy(ctx.mov);
			m_bOpen = false;
			m_mutex.unlock();
			int  last_pos = ftell(fp);
			fseek(fp,m_file_len - 1, SEEK_SET);
			fwrite("1",1,1,fp);
			fclose(fp);
			rec_mark_end(m_fileIndex,cur_pts,last_pos);
			m_start_tm = 0; 
			frm_cnt = 0;
			goto OPEN_NEW_FILE;
		}

		ret = mov_writer_write(ctx.mov, ctx.track, s_buffer, n, ctx.pts, ctx.pts, 1 == vcl ? MOV_AV_FLAG_KEYFREAME : 0);
		if (ret < 0)
		{
			m_mutex.lock();
		//	ctx.mov->mov.track->tkhd.width  = 960 ;
		//	ctx.mov->mov.track->tkhd.height = 576 ;
			mov_writer_destroy(ctx.mov);
			m_bOpen = false;
			m_mutex.unlock();
			int  last_pos = ftell(fp);
			fseek(fp, m_file_len - 1, SEEK_SET);
			fwrite("1", 1, 1, fp);
			fclose(fp);
			rec_mark_end(m_fileIndex, cur_pts, last_pos);
			PrintTimeMs(cur_pts, 0, "cur frame time");
			printf("------ Err ----WriteH264VideoData  ret %d frame_sz %d frmType %c \n", ret, frame_sz, frmType);
			frm_cnt = 0;
			continue;
		}
		if (last_pts != 0 && frm_cnt != 0)
		{
			diff = abs(cur_pts - last_pts);// -cur_pts;
		}
		else
			diff = 33;
		ctx.pts += diff;
		ctx.dts += diff;
		
		frm_cnt++;
		last_pts = cur_pts;
		diff = abs(last_pts - m_start_tm) / 1000;
		file_pos =  ftell(fp);


	}
	delete[] pBuf;
	delete[] s_buffer;
	return 0;
}


int CRecord_mp4_blk::GetNextFileName(string & filePath, int64_t start_tm, int event_id)
{
	struct tm *  tmStart;
	struct tm *  tmEnd;
	char fileName[120] = { 0 };
	char startTmSzBuf[64] = { 0 };
	uint32_t leaf_ms = (uint32_t)(start_tm % 1000);
	time_t startTime = start_tm / 1000;

#ifndef _WIN32
	startTime += 8 * 3600;
#endif
	tmStart = localtime(&startTime);

	int startYear = tmStart->tm_year;
	int startMonth = tmStart->tm_mon;
	int startMonday = tmStart->tm_mday;
	int dest_ch = 0;
	  
	int ret = m_pRecIndexSet->GetNextRecIndex(m_channel, m_disk_no, m_destDev, m_fileIndex, m_file_len);
	if (ret < 0)
	{
		if (ERR_NO_DEVICE == ret)
		{
			fprintf(stderr, "Device is lost.\n");
			return ret;
		}

		if (ERR_INDEX_NORECIDX == ret)
		{
			fprintf(stderr, "File index empty. ret %d\n", ret);
		}

		if (ERR_INDEX_NORECIDX == ret)
		{
			ret = m_pRecIndexSet->GetCycleIndex(m_channel, 0, m_destDev, m_disk_no, m_fileIndex, m_file_len);
		}

		TRACE("Dev %s Get reCycle m_diskIndex %d fileindex %d .\n", m_destDev.c_str(), m_disk_no, ret);
	}

	sprintf(startTmSzBuf, "%04d%02d%02d_%02d%02d%02d_%03d",
		tmStart->tm_year + 1900, tmStart->tm_mon + 1, tmStart->tm_mday, tmStart->tm_hour, tmStart->tm_min, tmStart->tm_sec, leaf_ms);

	
	sprintf(fileName, "%05d.%s", m_fileIndex, m_file_ext);
	filePath = m_storage_path;
#ifdef _WIN32
	filePath += "\\";
#endif
	filePath+=	fileName;

	if (m_show_log)
	{
		printf("GetFileName m_stream %d dest ch %d .\n", m_channel, dest_ch);
	}

	return 0;
}

int  CRecord_mp4_blk::check_path(const char * mnt_path)
{
#ifdef _WIN32
	return 0;
#else
	struct mntent* mnt;
	FILE* fp;
	char mmc_path[128] = { 0 };
	int ret = -1;
	fp = setmntent("/proc/mounts", "r");
	if (!fp)
	{
		return -1;
	}

	while ((mnt = getmntent(fp)))
	{
		if (m_show_log)
		{
			printf("mount info fsname %s dir %s.\n", mnt->mnt_fsname, mnt->mnt_dir);
		}

		if (strstr(mnt->mnt_fsname, "/dev/sd") ||
			strstr(mnt->mnt_fsname, "/dev/mmc")
			)
		{
			if (m_show_log)
			{
				printf("mount info %s \n", mnt->mnt_fsname);
			}

			if (access(mnt->mnt_fsname, 0) == 0 &&
				strstr(mnt_path,mnt->mnt_dir)
				)
			{
				ret = 0;
				goto CHK_DONE;
			}
		}
	}
CHK_DONE:
	endmntent(fp);
	return ret;
#endif
}

int CRecord_mp4_blk::get_usb_path(char * dev, char * usb_path)
{
	struct mntent* mnt;
	FILE* fp;
	char mmc_path[128] = { 0 };
#ifdef _WIN32
	return 0;
#else
	fp = setmntent("/proc/mounts", "r");
	if (!fp)
	{
		return -1;
	}

	while ((mnt = getmntent(fp)))
	{
		if (m_show_log)
		{
			printf("mount info fsname %s dir %s.\n", mnt->mnt_fsname, mnt->mnt_dir);
		}

		if (strstr(mnt->mnt_fsname, "/dev/sd") ||
			strstr(mnt->mnt_fsname, "/dev/mmc")
			)
		{
			if (m_show_log)
			{
				printf("mount info %s \n", mnt->mnt_fsname);
			}
			if (access(mnt->mnt_fsname, 0) == 0)
			{
				printf("find USB storage dev: %s \r\n", mnt->mnt_fsname);

				if (NULL != dev)
				{
					strcpy(dev, mnt->mnt_fsname);
				}
				if (usb_path != NULL)
				{
					strcpy(usb_path, mnt->mnt_dir);
					strcat(usb_path,"/");
				}
				goto CHK_DONE;
			}
		}
	}
CHK_DONE:
	endmntent(fp);
	return 0;
#endif
}

void CRecord_mp4_blk::rec_mark_start(uint32_t file_idx, int64_t start_tm,int record_type)
{
	memset(&m_recSegInfo, 0, sizeof(REC_FILE_INFO));
	m_recSegInfo.channel = 0;
	m_recSegInfo.indexNo = file_idx;
	m_recSegInfo.procstep = REC_PROCESS_OPEN;
	m_recSegInfo.dataSource = DATA_SOURCE_RECORD;
	m_recSegInfo.tmStart = m_recSegInfo.tmEnd = start_tm;
	m_recSegInfo.status = RECORD_FILE_USED | RECORD_FILE_RECORDING;
	m_recSegInfo.recordType = record_type;// RECORD_MANUAL;
	m_recSegInfo.timeZone = GetTimeZone();
	m_pRecIndexSet->RecordStart(m_recSegInfo);
}

void CRecord_mp4_blk::rec_mark_end(uint32_t file_idx, int64_t end_tm,uint32_t file_sz)
{
	m_recSegInfo.tmEnd = end_tm;
	m_recSegInfo.length = file_sz;
	m_recSegInfo.status = RECORD_FILE_USED;
	m_recSegInfo.procstep = REC_PROCESS_CLOSED;
	m_pRecIndexSet->RecordEnd(m_recSegInfo);
}
